//go:build hack

// SPDX-FileCopyrightText: 2022 k0s authors
// SPDX-License-Identifier: Apache-2.0

package genbindata

import (
	"compress/gzip"
	"errors"
	"flag"
	"fmt"
	"io"
	"os"
	"path"
	"strings"
	"text/template"

	"golang.org/x/sync/errgroup"
)

type fileInfo struct {
	Name                       string
	Path                       string
	TempFile                   string
	Offset, Size, OriginalSize int64
}

func compressFiles(dirs []string, prefix string) ([]fileInfo, error) {
	var tmpFiles []fileInfo

	// compress the files
	g := new(errgroup.Group)
	for _, dir := range dirs {
		files, err := os.ReadDir(dir)
		if err != nil {
			return nil, err
		}
		for _, f := range files {
			tmpf, err := os.CreateTemp("", f.Name())
			if err != nil {
				return nil, err
			}

			info, err := f.Info()
			if err != nil {
				return nil, err
			}
			filePath := path.Join(dir, f.Name())
			name := strings.TrimPrefix(filePath, prefix) + ".gz"
			tmpFiles = append(tmpFiles, fileInfo{
				Name:         name,
				Path:         filePath,
				TempFile:     tmpf.Name(),
				OriginalSize: info.Size(),
			})

			gz, err := gzip.NewWriterLevel(tmpf, gzip.BestCompression)
			if err != nil {
				return nil, err
			}

			inf, err := os.Open(filePath)
			if err != nil {
				return nil, err
			}

			g.Go(func() error {
				size, err := io.Copy(gz, inf)
				if err != nil {
					return err
				}

				fi, err := tmpf.Stat()
				if err != nil {
					return err
				}

				inf.Close()
				gz.Close()
				fmt.Fprintf(os.Stderr, "%s: %d/%d MiB\n", name, fi.Size()/(1024*1024), size/(1024*1024))
				return nil
			})
		}
	}

	err := g.Wait()
	if err != nil {
		return nil, err
	}

	return tmpFiles, nil
}

func GenBindata(name string, args ...string) error {
	var prefix, pkg, outfile, gofile string

	var bindata []fileInfo

	flags := flag.NewFlagSet(name, flag.ContinueOnError)
	flags.SetOutput(new(strings.Builder))
	flags.StringVar(&prefix, "prefix", "", "Optional path prefix to strip off asset names.")
	flags.StringVar(&pkg, "pkg", "main", "Package name to use in the generated code.")
	flags.StringVar(&outfile, "o", "./bindata", "Optional name of the output file to be generated.")
	flags.StringVar(&gofile, "gofile", "./bindata.go", "Optional name of the go file to be generated.")
	err := flags.Parse(args)
	if err != nil || flags.NArg() == 0 {
		buf := flags.Output().(*strings.Builder)
		if err != nil {
			fmt.Fprintln(buf, "Error:", err)
		}
		fmt.Fprintf(buf, "Usage: %s [options] <directories>\n", name)
		flags.PrintDefaults()
		return errors.New(buf.String())
	}

	tmpFiles, err := compressFiles(flags.Args(), prefix)
	if err != nil {
		return err
	}

	outf, err := os.Create(outfile)
	if err != nil {
		return err
	}
	defer outf.Close()

	var offset int64

	fmt.Fprintf(os.Stderr, "Writing %s...\n", outfile)
	for _, t := range tmpFiles {
		inf, err := os.Open(t.TempFile)
		if err != nil {
			return err
		}

		size, err := io.Copy(outf, inf)
		inf.Close()
		if err != nil {
			return err
		}
		os.Remove(t.TempFile)

		t.Offset = offset
		t.Size = size
		bindata = append(bindata, t)

		offset += size
	}

	f, err := os.Create(gofile)
	if err != nil {
		return err
	}
	defer f.Close()

	return packageTemplate.Execute(f, struct {
		OutFile     string
		Pkg         string
		BinData     []fileInfo
		BinDataSize int64
	}{
		OutFile:     outfile,
		Pkg:         pkg,
		BinData:     bindata,
		BinDataSize: offset,
	})
}

var packageTemplate = template.Must(template.New("").Parse(`//go:build !noembedbins
// Code generated by go generate; DO NOT EDIT.

// datafile: {{ .OutFile }}

package {{ .Pkg }}

var (
	BinData = map[string]struct{ offset, size, originalSize int64 }{
	{{- range .BinData }}
		"{{ .Name }}": { {{ .Offset }}, {{ .Size }}, {{ .OriginalSize }}},
	{{- end }}
	}

	BinDataSize int64 = {{ .BinDataSize }}
)
`))
